feat(manifest): authoritative agent-bundle.manifest.json v2 — consumers read the composite root through the manifest (#592 step 3, #555 W2/S3) - #604
Conversation
…555 S1) - config/normalize.ts no longer synthesizes a plugin lowering target or a shared plugin skill document; loweringHosts is a pure isSkillHost filter. - adapters/registry.ts registers portable, codex, claude, cursor only; adapters/plugin.ts is deleted together with the composite-only capability helpers (intersect/union/mergeCapabilityEvidence, capabilityBooleanView). - targets: ['plugin'] and --target plugin fail with the existing AB4100. - plugin branches removed from api.ts, pack-inventory, package-build, entry-shell, events/projection, lifecycle replay, install/surface, install/install, create-agent-bundle options/scaffold, capability JSON, and the CLI help text. - Tests: plugin-bundle.test.ts deleted; plugin rows dropped from the adapter, install-surface, normalization and lifecycle suites; acceptance 3 covered by normalization.test.ts and cli.test.ts.
S2) build/build.ts stages every selected host projection into one root at artifactRoot; build/compose.ts merges byte-identical entries once, fails same-path collisions with AB4103 in host-name/path order, and refuses host-scoped components another selected host would discover conventionally with AB4105. Codex and Cursor hook/MCP documents move beside their manifests; shared hooks compile to hooks/<name>.<host>.mjs; compiled surfaces are attributed to the sorted composite identity; the artifact manifest records only the selected projections. Install, doctor, dev services, eval harnesses, Workbench, examples, and tests follow the composite root.
…inspect judge the composite root; port #569 consumer and docs fixes - config/validate.ts: AB4106 when an adapter registered on an advanced TargetRegistry is selected beside any other target; the built-in host list moves to adapters/composite-layout.ts and install/surface.ts imports it. - build/compose.ts: planComposite returns the plan beside its diagnostics; dev/project-service.ts prepare uses it so validate, inspect, dev report AB4103/AB4105 exactly where build refuses (caught by the composite-rules parity test ported from #569). - Ported from #569 (superseded): tests/support/mcp-conformance.ts pluginRoot, tests/skill-document-service.test.ts decoy, examples/audiobook-curator and examples/hooks-and-scripts READMEs, scripts/measure-hook-cold-start.mjs. - tests/composite-rules.test.ts: layout-independent rules from #569 (four-host root, commands/ dialect collision, INSTALL.md sections, validate parity). - Stale per-host wording: Cursor capability evidence, doctor/types/cursor comments, create-agent-bundle README, docs/diagnostics.md, canvas sample.
…akes into a generated-route server; AB4105 trigger names skills
… root; mcp-apps-compile test selects the portable projection
…e selection identity (codex+portable), matching planCompiledMcpApps after the #585 merge
…ot-w1 # Conflicts: # rstest.integration-tests.ts
…nested-root and shared-root tests (#592 boundary) The generated MCP entry and every hook wrapper derived the event runtime's endpoint id from `<epoch>:<selection identity>:<root>`, so the composite selection (`claude+codex`) had become runtime identity, and the runtime took `events.target` as the tool-call lineage host fallback, reading the selection as a host. Both are projection selection leaking into runtime identity (#592 §2). The endpoint is now `<epoch>:<root>` on both sides (entry-shell, hook-contract, `agent-bundle/test` installed harness), the `target` field leaves `GeneratedRouteMcpEntryOptions` and `GeneratedEventRuntimeBinding`, and the lineage fallback is the one host a single-projection root serves, or none for a composite root; `entries.ts` requires the selection instead of defaulting it from the composite name. `AB4105` never fired for a skill: normalization gives every skill every selected target and per-host frontmatter extensions collide as `AB4103` instead, so the dead skill branch leaves `compose.ts` and the diagnostics, reference, and framework-mode prose say so. `AGENT_BUNDLE_HOOK_HOST` leaves the runtime-environment reference (en/zh) and the test env fixtures; `api.ts` reuses `isBuiltInHost`; `compose.ts` reuses `sortedProjections`; stale `<target>/…` doc comments in `test/packed.ts` and `routes/graph.ts` name the root layout. Ports from #569: install refuses `--from` naming a directory above the plugin root for all three hosts (`AB7001`, no host CLI runs), doctor lists Claude plugins from the root `--from` names and never from a nested `claude/`, and the Codex validator judges only `.codex-plugin/*` in a root shared with Claude's `.mcp.json` and `hooks/hooks.json`. Lane C's docs parity pass: tree drawings gain `commands/` and `rules/`, the folder-discovery shield names all three guarded paths, over-wide code samples re-padded to 90 columns, `AB4808`/`AB4809` prose and the rsc-agent-runtime README describe one composite root.
…ost-packs/runtime/…), not under a target partition
… server; document AB7001's composite-root trigger Self-review pass 1 read the fallback as inferring the root's cardinality from `allowedTargets`. It is `server.targets ∩ selected` — the hosts whose MCP documents list the server, so the hosts that can have spawned it — and a Claude-only server in a Claude+Codex root is correctly assumed to be Claude's. The comment and the two test names now say so. `docs/diagnostics.md` names `AB7001` in the `AB700x` family row: the host manifest sits directly under `--from`, never under `<from>/<host>`.
…rst generated server; judge built-in hosts by adapter identity for the install surface (#578 review) - planMcpEntriesSurface no longer attaches every event route to the first generated-route server: eventRuntimeHosting hosts the runtime in the first generated server each selected host's MCP document lists (one process when they agree, one per host otherwise) and every hosting server accepts the same allowed set; a Claude-only server in a Claude+Codex root no longer refuses Codex wrappers while Codex's own server hosts nothing. - The generated entry carries `hosts` (the selected hosts whose documents list the server) separately from `allowedTargets`; the lineage fallback reads `hosts`, so a Claude-only server hosting a two-host runtime still assumes Claude for an anonymous MCP client (#592). - TargetRegistry.builtInHost()/builtInHosts() judge the four shipped adapters by identity; compose and artifact validation use it for INSTALL.md and install.mjs, so an advanced registry's adapter named `portable` earns no install surface it never asked for. The pack inventory (manifest names only) keeps the name-based requirement it had on main.
…s, distribution; hook index folded into executables.hooks (#592 step 3)
… feat/592-authoritative-manifest
…ion, never as the selection identity The browser pool compiles every app once for the project's whole selection (as the build stages it, #555) but the registry's `target` — the preview profile and the binding's `target` the page sees — is the host the app mounts as: the override, or the app's first declared target the project selects, as before. `claude+codex+portable` had leaked into the binding (#592) and failed examples/mcp-app's browser-app suite in CI.
The hook-index sidecar is gone; these tests now assert executables.hooks on agent-bundle.manifest.json and pass the required route graph into build().
🦋 Changeset detectedLatest commit: bb241b2 The changes in this PR will be included in the next version bump. This PR includes changesets to release 1 package
Not sure what this means? Click here to learn what changesets are. Click here if you're a maintainer who wants to add another changeset to this PR |
… feat/592-authoritative-manifest
# Conflicts: # docs/diagnostics.md # website/docs/en/guide/distribution/installation.mdx # website/docs/en/reference/cli.mdx # website/docs/en/reference/configuration.mdx # website/docs/zh/guide/distribution/installation.mdx # website/docs/zh/reference/cli.mdx # website/docs/zh/reference/configuration.mdx
… AB4100 lists the four hosts, AB6023/AB6024 and AB7001 describe the one root
…) into feat/592-authoritative-manifest
…ointer; admit the compile evidence record
|
CI integration-2 diagnosis: I downloaded artifact 9976012542 and inspected the Playwright trace. The product close completed; the trace shows the Close MCP session click at ~6.67s, the button disabled, and the terminal render replaced it with Reset MCP session by ~6.70s. The test then waited 30s on |
…e server identities, malformed launch and projection rows; reindex refuses compiled files and compile evidence
… into feat/592-authoritative-manifest
…; copies re-measure to the verified inventory
…AB4741 for payloads, files[] parser and schema, installers
… into feat/592-authoritative-manifest
# Conflicts: # packages/agent-bundle/tests/web-host-routes-unit.test.ts
…ifest, the runtime state root, or the install receipt entry
… into feat/592-authoritative-manifest
|
Re the integration-2 diagnosis (20:12): the assertion race is already fixed on this branch — Head is now |
…r, transport, pointer (AB6017); install re-measures mode; one portable path rule (#604 follow-up) (#650) * fix(manifest): launch entries are the rows their server kind compiles to; host documents agree with the launch record; install re-measures bytes and mode; one portable path rule for files[] and receipts * mcp run: host document must project every launchable manifest server; drop manifest-only fallback * chore: deslop the Pass 8 fix delta * fix(validate-artifact): host documents project the launch record in order, over stdio, from the manifest's own pointer (AB6017); schema device-name parity cases * changeset: manifest launch agreement follow-up
Implements #592 step 3 — the authoritative manifest together with #555 W2/S3 — consumers read the composite root through the manifest. Design comment: #592 (comment). Owner guidance from the 08:30 review is implemented, not noted — see "Owner review (08:30)" below.
What changes
agent-bundle.manifest.jsonismanifestVersion: 2: closed keys, canonicalstableJsonbytes, every path root-relative POSIX, and it is serialized from the compiler's own IR (NormalizedPlugin→CompiledRouteGraph→TargetArtifactPlans) rather than reconstructed. The host documents (.claude-plugin/plugin.json,.codex-plugin/*,.cursor-plugin/plugin.json,.mcp.json,hooks/hooks.json, marketplace catalogs) are projections the manifest points at; nothing reads them for identity any more.Public contract (durable)
manifestVersion2applicationid,name,version,description?— identity, onceprojections[]host(the selected name),builtInHost?(the shipped adapter that planned it — identity, never the name),documents{plugin?,marketplace?,mcp?,hooks?}pointers,marketplace{name}?routesdigest,servers[]{id,name,mode,routes[]},events[]{…,execution{preflight?,providers[],runtime,fallback}}(#618: the preflight export and required providers artifact-only inspection needs),scripts[],providers[],layouts[],cli?{mode,routes[],commands[]}—commands[]carryoptions[].aliasesandprojection?{module,mapInput,defaults?,relaxed?}serialized fromCompiledCliProjection(#616),contracts?[]{id,input,origin,routes[]}(reserved shape for #593)executablesbins[]{name,path,worker?,hosts[]},hooks[]{id,name,event,host,kind,path,routeId?,timeout?},mcpServers[]{id,name,kind,transport,hosts[],launch?{entry,worker?,args[],env},apps[]{id,name,resourceUri,path?,prebuilt?}}—kindiscompiled/prebuilt/command/remote;launchis present exactly forcompiledandprebuiltand is the one launch record every host MCP document projects and<plugin> web/mcp runstart (args[]are{kind:'artifact',path}for root-relative references written fromagent-bundle:path:plugin-root/…, else{kind:'literal',value}; tokens in literals andenvare expanded by the launcher, never by the manifest),scripts[]{id,name,mode,path,worker?,hosts[],rendered?}distributionchannels[](local/npm),install?{instructions?,script?}— pointers only; S5 owns the contents — andpayloads[]{name,hosts[],runtimeDependencies[]}: everydefinePrebuiltdirectory a selected host packages (#630), thefiles[]rows under<name>/are itsprebuiltcontentsfiles[]path,bytes,sha256,kind,mode?— the authoritative inventory (no provenance here)runtimenodeweb?apps[]{app,name,resourceUri,server,tool?,input?,allow[]}+open;apps[].servernames anexecutables.mcpServers[]row that carrieslaunch(#628/#633 — one launch owner, no second server definition)Compiler record (operational, versioned separately)
compiler.recordVersion: 1withproducer{name,version},project{configPath,configDigest,modelDigest,revision,sourceInputs[],packageName?,packageVersion?},provenance[]{path,sourceInputs[]}(paths exactly matchfiles[]),adapters[]{host,adapterRevision,observedVersion,schemas[]},agentSkills,validation. Changing anything here never changes the public contract;manifestVersionandrecordVersionmove independently.targets[]→projections[];validation.targets[]→validation.projections[];files[].sourceInputs→compiler.provenance[];producer/project/agentSkills/validationmoved undercompiler. Theagent-bundle.hooks.jsonsidecar is gone (build/hook-index.tsdeleted); hook rows areexecutables.hooks[].Shipped schema
packages/agent-bundle/schemas/agent-bundle.manifest.schema.json(draft 2020-12,additionalProperties: falseeverywhere, infiles), exported asartifactManifestSchemaandvalidateArtifactManifestSchemafromagent-bundleandagent-bundle/api, besidereadArtifactManifest,parseArtifactManifest,artifactManifestName,artifactManifestVersion,artifactCompilerRecordVersion.tests/manifest-schema.test.tsproves the writer's output validates, the schema's keys agree with the TypeScript type, and closed-key extras are rejected by both.Consumers rewired (probing deleted in the same change)
install,uninstall,doctor --frominstall/identity.tsreadBundleIdentity: identity fromapplication, projection bybuiltInHost === host, host document + marketplace pointers fromprojections[].documents; copy set and content hash fromfiles[](install/receipt.tsmanifestInventory)resolveBundleRoot,readIdentity,manifestPath/marketplacePath/readRecord/readStringindoctor.tsandinstall.ts, theplugin.json/ marketplace readers, the directory walk that produced the content hashmcp list|invoke|run,serve-appbuild/manifest-projection.tsresolveManifestHost/resolveManifestMcpDocument: server rows fromexecutables.mcpServers[],--targetoptional when one projection runs the server;mcp runlaunches the host document's own line, cross-checked againstexecutables.mcpServers[].entryhostManifestPaths,mcpDocumentPath,configureArtifactOptionstarget-required pathshooks list|simulate,AB6018executables.hooks[]readArtifactHookIndex,parseArtifactHookIndex,writeHookIndexinspect --json, newinspect --artifact <root>services/inspect-artifact.ts→dev/artifacts/application-explorer.tsapplicationExplorerFor— a thin UI projection of the manifest, not a mirrorartifact-executables.tsfromexecutables.*; scripts, bins, MCP servers, hooks from rowsartifact-script-catalog.ts(directory listing ofscripts/)dev/host-install-manager.ts)build/manifest-reindex.tsreindexArtifactManifestre-indexes the variant'sfiles[]+provenance[]after it editsmcp.json/adds markerspack-inventory,eval --artifact,test installedfiles[],projections[]targets[]readersDiagnostics (
docs/diagnostics.md)AB6010now also judges adapter identity:projections[].builtInHostmust equalTargetRegistry.builtInHost(host)besides revision/version/schemas.AB6018is judged fromexecutables.hooks[], not the deleted sidecar.AB7001sources from the manifest: missing or non-canonical manifest, no projection whosebuiltInHostis the host, missingdocuments.plugin, pointer to a file the root lacks, afiles[]row missing or digest-mismatched, unsafe Cursor name, Claude/Codex withoutmarketplace.name.doctor --fromreports the sameAB7001(with a Doctor recovery) instead of folding it intoAB7306;AB7306remains for non-diagnostic failures.AB6039/AB6040existed in an earlier revision as drift detectors between the manifest and host documents; removed per the 08:30 review — the adapter runtime/hook contracts are the single source fordocuments.mcp/documents.hooks, so there is no second source to drift.Owner review (08:30)
manifestFortakes theCompiledRouteGraph, theNormalizedPlugin, the selectedTargetArtifactPlans and the registry contracts;documents.mcp/documents.hookscome fromregistry.mcpRuntime(host).manifestPath/registry.hookContract(host).manifestPath.AB6039/AB6040deleted.compilerrecord with its ownrecordVersion(above).core/paths.tsisRelocatablePosixPathgates every path field in writer and parser (must be a safe relative POSIX path);tests/manifest-relocatable.test.tsbuilds a four-host fixture with hooks, scripts, a CLI bin, a compiled MCP server, an MCP App andweb, asserts no machine path appears in the bytes, every pointer is afiles[]row, moves the root to another directory, and re-runsvalidateArtifact,readArtifactManifest,readBundleIdentity(three hosts) and the MCP pointer resolution against the moved root — byte-identical manifest, same answers. Forged absoluteexecutables.mcpServers[].entry.pathandweb.apps[].entryare rejected.cargo-hauler proof (#107 §1) — copied artifact, no source, no
node_modulesFull report and traces:
/tmp/592-proof3/report.md,/tmp/592-proof3/{out,traces}/. Head proved:464f30ca8(the reviewer fixes after it touchdoctorerror codes,mcp runargument order and tests only). Packed from a detached worktree:agent-bundle-0.1.0.tgz164cd05d…111a(3,403,441 B),agent-bundle-runtime-0.0.0.tgz25e9cd0b…9343,rsc-markdown-stream-0.1.0.tgz5c154ac6…01fa. Scratch cargo-hauler at/tmp/592-proof3/hauler(source checkout untouched),pnpm-workspace.yamloverrides → the tarballs,pnpm install+agent-bundle build→ compositeartifact/(noartifact/<host>),validate --artifact0 errors, Claudeplugin validateloaded.Manifest:
manifestVersion2,compiler.recordVersion1,applicationcargo-hauler@0.6.0, projectionsclaude/codex/cursor/portable, 37files[],executables.bins[0]bin/cargo-hauler.mjs+bin/cargo-hauler-flight.mjsworker, one compiled MCP serverhaulerwith Apphauler/dashboard, 12 hooks.cp -r artifact /tmp/592-proof3/root-…thentest ! -eforagent-bundle.config.ts,src,node_modules,package.jsonunder the root and the cwd. Every command traced withstrace -f(openat/statx/getdents64/execve/clone), filtered to the copied root:<root>/<host>doctor --host cursor|claude|codex --from ROOT --jsonbundleRoot,0.6.0,artifactContentHash 56c1eff722af…fromfiles[]~/.cursor0.6.4 comparison (AB7309), 0 for claude/codexinstall cursor --from ROOT --json(no--replace)AB7005version collision vs installed 0.6.4; wrote nothingmcp list --target cursorvalidateArtifactwalks the tree firstmcp list(no--target)AB5000names the four projectionsmcp invoke hauler_status --target cursorROOT/mcp/mcp-hauler-2b8242f9-flight.mjsserve-app hauler/dashboard --target cursor --no-open --no-env<title>hauler/dashboard</title>inspect --artifact ROOT [--json]{ manifest, application }— theApplicationExplorerpayload the Workbench rendersvalidate --artifact ROOTfiles[]doctor --from ROOT/.cursor-plugin,--from ROOT/cursorAB7001 No agent-bundle.manifest.json in …Corrupted copies (
/tmp/592-proof3/hauler-corrupt): deletingprojections[cursor].builtInHost→AB7001 … built for projections [claude, codex, cursor, portable]; none is the shipped cursor adapter; garbled JSON →AB7001 … not a valid canonical artifact manifest: Artifact manifest is not valid JSON.;documents.plugin→does-not-exist.json→AB7001 … names "does-not-exist.json", which is not a manifest file.— on bothdoctorandinstall --json.Residual filesystem access, stated plainly:
mcp list|invokeandserve-appstill runvalidateArtifactbefore launching — the pre-existing tamper gate that compares the tree againstfiles[](that is thegetdents64in the table). It is not probing for structure; every launch decision comes from the manifest. Narrowing it to the executed rows is a follow-up, not this PR.doctorstats fixed names (state,.env,.env.local; for Claude.lsp.json,monitors/monitors.json,settings.json) — operator data and Claude's project layout, not artifact structure; and the host CLIs it spawns (claude plugin list) walk the root themselves.ApplicationExplorerthatinspect --artifactprints, from the manifest only (tests/application-explorer.test.ts,packages/workbench/tests/examples-real.e2e.test.ts).tests/version-consistency.test.tsstill opensartifact/cursor/.cursor-plugin/plugin.jsonand fails withENOENT— the consumer change meta-framework: execution plan and sequencing #107 §1 asks for; not modified here.Absorbed from
mainafter the owner's go (#616 / #630 / #629)Merged
origin/maintwice (7769b21fc: #629 + #630;472d64d1a: #616 + #628). Nothing on the branch keeps a second copy:routes.cli.commands[].projection{module,mapInput,defaults?,relaxed?}andoptions[].aliasescome fromCompiledCliProjection/CompiledCliOptionviabuild/manifest-routes.ts;AB4843–AB4845stay compile-time (the compiler is the only judge; the manifest records the result).dev/routes/route-manifest.tsnow aliases the manifest's CLI types instead of declaring its own copies. Schema, parser,tests/manifest-schema.test.tsparity rules,tests/cli-routes-build.test.ts(built artifact carries the projection), en/zh reference updated; "Reserved keys" no longer listsprojection.definePrebuilt/runtimeDependencies—distribution.payloads[]is serialized fromNormalizedPlugin.payloads, filtered to the selected hosts and sorted; the parser requires each payload directory to hold at least oneprebuiltfiles[]row andpayloads[].hoststo name declared projections. Test:tests/prebuilt-payload.test.ts(records a payload only for the selected hosts it targets …).web-host/manifest.tsreadWebManifestDocumentread the v1targets[].namelist for the declared projections; on manifest v2 that isprojections[].host, so the dev/webroute saw no launchable projection (CIintegration-1:dev-web-host-routes.test.ts404). Reader andweb-host-routes-unit.test.tsfixture moved to the v2 shape; no v1 fallback kept.ArtifactInspection(which the inspection service derives from the manifest);ArtifactInspectionMcpServergainedtransportso the Application tree keeps labelling servers by transport whilekindmeanscommand|compiled|remote. Workbench validator (isMcpServer) and fixtures updated;application-tree-model.test.tsasserts the label.Owner audit (17:11) — contract table
Each row done on the integrated head; the fresh reviewer passes below cover the result.
routes.cli.commands[].projection{module,mapInput,defaults?,relaxed?}andoptions[].aliases, serialized fromCompiledCliProjectionbybuild/manifest-routes.ts;defaultsare the CLI-onlyflags.<key>.defaultliterals (the canonical Zod defaults are not re-derived).cli.projectionSourcesis deliberately not recorded: it maps route ids to absolute module paths for the bundler (build-side only, excluded from the graph digest for the same reason) and would violate the relocatability rule; the relativeprojection.moduleis the recorded identity.tests/cli-routes-build.test.ts,tests/manifest-schema.test.ts,tests/manifest-key-inventory.test.tsroutes.events[].execution{preflight?,providers[],runtime,fallback}fromroutes/event-execution.tseventRouteExecutionFor— the same helperconfig/normalize.tsuses, so there is one derivationtests/manifest-combined-proof.test.tsasserts the block;tests/preflight-artifact-graph.test.tsweb-host/manifest.tsreadWebManifestDocumentreadsprojections[].host(v1targets[].namegone) and now requiresmanifestVersion === 2; the session-selection path (dev/web-host-launch-selection.ts,web-host/launch.ts) takes the launch fromdocument.launches.get(app.server)tests/web-manifest.test.ts(hosts, launches, version rejection forundefined/1/3/'2'),tests/web-host-routes-unit.test.ts,tests/dev-web-host-routes.test.tsexecutables.mcpServers[].launchis the only launch record;web.apps[]lostentry/args/env;<plugin> webresolves it through the sharedexpandLaunchTokens(web-host/manifest.ts), artifact arguments under the root, env tokens under the durable root,AGENT_BUNDLE_PLUGIN_ROOTanchored;mcp runlaunches the host document's line for the record of the same name and refuses a document that renames or omits a launchable server (AB6017, Pass 8)tests/manifest.test.ts,tests/prebuilt-payload.test.ts(prebuilt args/env through the web launcher),tests/build-compose.test.ts(custom adapter order; renamed document server refused),tests/packed-readonly-state-root.test.tsArtifactInspection.application(dev/artifacts/application-explorer.ts); Routes/Hooks/Playground pages and tests stay deleted as onmain;transportadded so the tree keeps labelling servers by transportpackages/workbench/tests/application-tree-model.test.ts,artifacts-page.test.ts,examples-real.e2e.test.tsdistribution.payloads[]{name,hosts[],runtimeDependencies[]}fromNormalizedPlugin.payloads(selected hosts only); prepack behaviour untouched; artifact-only consumers read the deployment requirement from that row, and prebuilt servers carrykind: 'prebuilt'+launchtests/prebuilt-payload.test.ts,tests/manifest-schema.test.ts,tests/manifest-combined-proof.test.tsmanifestVersion— adding an optional public key included; "optional" is never "backward compatible".compiler.recordVersiongoverns only thecompilersubtree. Enforced bytests/manifest-key-inventory.test.ts: the shipped schema's full key inventory (public and compiler, split) is checked into fixtures; changing either without bumping the matching version fails. Every reader —parseArtifactManifestand the lean web reader — refuses any othermanifestVersion.tests/manifest-key-inventory.test.ts,tests/manifest.test.ts,tests/web-manifest.test.tstests/manifest-combined-proof.test.ts: one fixture with a projected CLI tool (submit.cli.ts), a preflight event, an App exposed throughweb, durable framework state (agent-bundle:path:plugin-data/stateinenv), and adefinePrebuiltpayload withruntimeDependencies; built, copied to a relocated directory (no source, nonode_modules), byte-identical manifest,validate/doctor/inspect --artifactfrom the copy, theninstall.mjsinstall → same-version replace (afterreindexArtifactManifest) →doctor→ uninstall with the exact success lines pinned (Installed …@… at <destination> (content …),Replaced … (content … -> …),Uninstalled … for cursor (local mode) at <destination>) andcollision/daemon version mismatch/Already installedasserted absent, and the same artifact exercised through the fixture'spackage.jsonbintests/manifest-combined-proof.test.ts(integration pool)envkeeps theagent-bundle:path:plugin-data/…token unexpanded,webrecords no data root, and the launcher (not the manifest) derives~/.agent-bundle/web-data/…/~/.agent-bundle/state/…. After merging #640 and #642,install/state-root.tslocates a declaredAGENT_BUNDLE_STATE_ROOTthrough the installed manifest'sprojections[host].documents.mcppointer instead of probing.cursor-plugin/mcp.json/mcp.json; coordination comments exchanged on #604 (17:25 / 17:28).tests/packed-readonly-state-root.test.ts,tests/uninstall.test.ts,tests/doctor.test.ts,tests/manifest-relocatable.test.tsMerged
origin/mainthrough #636, #638 (compile evidence record — admitted as root metadata invalidate-artifact.ts; the hook-index half of that merge stays deleted), #640 and #642.Deslop
Deslop:
gpt-5.6-sol-medium, 11 edits proposed / 9 applied on the pre-merge diff (365939501,0e34c653a; two reverted: the "Reserved keys" sections in the en/zh manifest reference, which the brief requires) + 1 edit on the post-merge delta (ff3d3efc0) + 1 edit on the #616/#630 absorption delta (59dbb1ee5; the other proposals touchedmain's own comments outside the delta and were not applied) + 2 edits on the 17:11 contract-table delta (6b10803f2: temp-home cleanup inprebuilt-payload.test.ts) + 2 edits on the #638/#642 merge delta (2387162ac: two restating comments) + 2 edits on the P2 reader/reindex delta (c78bfc8d2) + 5 file-level edits on the post-74ccb99 delta after the #634 merge (bb241b281: 12 restating comments inbuild.ts,build-compose.test.ts,prebuilt-payload.test.ts,web-manifest.test.ts; three non-null assertions inparseRoutesreplaced by a type-narrowing filter) + 10 edits on the Pass 8 fix delta (1db705bc5, Sol: 13 proposed, 3 reverted where the comment gave the reason rather than restating the code). Changeset condensed by hand.Self-review
Reviewer:
gpt-5.6-sol-medium(author lanes: Fable/Sol/Grok; integrator Fable).Pass 1 (on
464f30ca8) — 4 findings, all fixed:tests/preflight-artifact-graph.test.tsstill readagent-bundle.hooks.json→ readsmanifest.executables.hooks(6f69911f2).doctor --fromfolded everyAB7001intoAB7306, contradicting docs/changeset →malformedBundlepreservesDiagnosticErrordiagnostics;AB7306only for non-diagnostic errors; test updated (ff3d3efc0).mcp runrebuilt the launch as[manifestEntry, ...hostArgs.slice(1)], wrong for adapters that put flags first or use the entry ascommand→ the host document's line is used verbatim; a document that skips the compiled entry is already refused at build (AB6017), so no runtime re-check (ff3d3efc0,fe49a137a).tests/manifest-relocatable.test.tsomittedweb.apps[].entry→ fixture gained an MCP App +web; absolute and unlisted web entries asserted rejected (ff3d3efc0).Pass 2 (on
60148f923) — 5 findings, 4 fixed, 1 dismissed (fe49a137a):relativePathacceptedC:escapewhile the parser rejects it →(?![A-Za-z]:)lookahead added; parity case intests/manifest-schema.test.ts.tests/build-compose.test.tslaunches a synthetic adapter whose document is--enable-source-maps <entry> --stdioand asserts the exact sequence, and asserts the build refuses (AB6017) a document that skips the entry.DiagnosticErrorAB7306fallback lost its test →tests/doctor.test.tsmakes.cursor-plugina regular file (ENOTDIRon the pointer check) and assertsAB7306with noAB7001.AB7306had no row indocs/diagnostics.md→ "Read-only Doctor bundle resolution (AB7306)" section added; coverage check green.cli.tsinspect --artifactusesawait import('./api.ts')— dismissed: that is the module's documented policy (header comment, lines 7–16) and every other action in the file does the same so--help/--versiondo not load the product graph.Pass 3 (merge delta vs
origin/mainafter absorbing #616/#630/#629, on59dbb1ee5) — 5 findings, all fixed (2d5eb053a), reviewer re-run: "No findings":manifestPayloadshost filtering untested →tests/prebuilt-payload.test.tsbuilds withtargets: ['claude', 'portable']against payloads targetingcodexandclaude, asserts onlyruntimeis recorded withhosts: ['claude']and noapp/rows are staged.ArtifactInspectionMcpServer.kindas the transport label afterkindchanged meaning →transportadded to the inspection row, service, Workbench validator, model, and fixtures.distribution.payloads[<name>]location string →distribution.payloads[<index>].name, matching the rest of the parser.docs/diagnostics.mdomitteddefaults?from the projection field list → added.{@link CompiledCliProjection}inroute-manifest.ts/manifest.tsrelies on TypeDoc's project-wide resolution — dismissed:pnpm docs:site:buildresolves it (0 broken links) and the file's other links use the same form.Pass 4 (contract-table delta, on
74ccb996b) — 5 findings, all fixed (17edaf120,6b10803f2):mcp run's no-host-document fallback dropped the manifest launchargs/env→ built fromrow.launchwithexpandLaunchTokens(shared withresolveWebLaunch), artifact arguments under the root, env under the durable root.web-host/manifest.tsnever checkedmanifestVersion→ requires 2; rejection tests forundefined/1/3/'2'.for cursor (local mode)).args/envthrough the launch record and web launcher →prebuilt-payload.test.tsadds a server with literal/artifact args and tokenized env, asserts the record and the resolved launch (state underhome, not the artifact).launch/web servers are compiled-only → say compiled or prebuilt.Pass 5 (fresh, integrated result incl. #640/#636, on
6b10803f2) — 2 findings, both fixed (9b8ce27e9,66ac3a7bf), reviewer re-run: "No findings" on code, docs precision only:AGENT_BUNDLE_PLUGIN_ROOT, so a generated server could derive state from the artifact → anchored on the durable root;build-compose.test.tsreaches the fallback through a registry whose reader aliases server names (AB6017 still holds) and asserts the anchor.<plugin> websupport) and what it checks; "nine required keys plus optionalweb"; Versioning notes the lean reader rejects the version mismatch first.Pass 6 (#638/#642 merge delta, on
93fa0e1c9) — 1 finding, fixed (2387162ac): the doctor row claimed a manifest-less root always gets the derived state root; it falls back to an inheritedAGENT_BUNDLE_STATE_ROOTfirst. Verified by the reviewer:readArtifactManifestnever throws on arbitrary directories, no import cycle frominstall/tobuild/manifest-file.ts, install copies the manifest so the three callers see it, the manifest is excluded from the ownership walk, no live reference to the hook index remains.Pass 7 (owner P2 + evidence items, on
d31aca73a…d1018dedb) — 7 findings, 6 fixed, 1 dismissed:launch.entry/worker/artifactargs againstfiles[]→parseFilePaths+requireLaunchFilesshared with the full parser (8fa261880).parseProjectionHosts/parseServerLaunchesvalidate only the slices the lean reader launches from — dismissed by design, documented in the "Lean reader" row: it refuses rather than filters those slices and ignores the rest.copyInventoryFilesverified the source and copied without re-hashing (TOCTOU) → the copy is re-measured and refused when it is not the verified inventory (8fa261880; test ininstall.test.ts).reindexArtifactManifest→ reference rows say reindexing re-measures non-compiled rows and refusesbundlerows and the compile evidence record (8fa261880).statewas indexed by the manifest but skipped bytreeInventory, so a valid artifact failed the copy hash →state/is reserved everywhere:AB4741for the payload, both parsers and the schema refusefiles[]rows under it in any letter case (c97457521).state/→ oneparseArtifactFilePathused by both readers (d1018dedb)..agent-bundle-install.jsonpassed the parser buttreeInventoryrejects it → the same rule reserves the receipt entry (d1018dedb).Pass 8 (fresh, rebased result through #634/#646, on
bb241b281;change-risk-reviewercould not start over tracedecay transport, fell back togeneralPurposeon Sol) — 3 findings, all fixed (1af72fec00,66c72f458):requireLaunchFileschecked path membership only: a compiled server'slaunch.entrycould name agenerated/copyrow (bytes no compile evidence describes) and two servers' records could be swapped without any check against the host documents → (a) both readers require the entry to be the row its server kind starts (bundleforcompiled,prebuiltforprebuilt) and the worker abundlerow (parseFileKinds, sharedartifactManifestFileKinds); (b)validateMcpCoherenceholds every host document to the launch records projected to its host: every launchable manifest server for the target must appear in the document under the same name (validateDeclaredServersPresent), and each stdio document server's artifact-local paths must include its record's entry andartifactarguments (validateLaunchAgreement, a subset check so a./payload/fileliteral the adapter resolves artifact-locally is not rejected), elseAB6017. With drift refused at validation,mcp run's manifest-only fallback became unreachable for any validated root and is deleted (66c72f458): the host document is required and is the launch line. Tests:web-manifest.test.ts,manifest.test.ts(wrong-kind entry, prebuilt at a bundle),artifact-validator.test.ts(swapped record →AB6017; renamed server →AB6017; agreeing record with an artifact-local literal → none),build-compose.test.ts(aliased document names →mcp runrefuses withAB6017, nothing spawned).manifestInventorycomparedsha256only, so a post-buildchmodon an indexed row installed as authoritative → usesmatchesManifestFile(bytes, mode, path, digest) likevalidate-artifact;install.test.tsassertsAB7001on an executable bit the row does not declare. Test fixtures (support/install-fixture.ts,public-api-packed.test.ts) now recordmodeandbundlekinds the way the compiler does.NUL,CON.txt,a:b, trailing dot/space, control characters), so a valid manifest could be uninstallable → oneisPortablePathSegmentincore/paths.tsused byisRelocatablePosixPath(both parsers) andisReceiptPath; the schema'srelativePathpattern mirrors it; five parity rules added tomanifest-schema.test.ts.Pass 9 (re-run after the Pass 8 fixes, on
1db705bc52, Sol) — 3 must-fix, 2 should-fix, all fixed (1ee3cffae5):node payload/bootstrap.mjs mcp/server.mjspassed while Node ran the bootstrap →validateLaunchAgreementtakes the document's artifact-local paths in launch order: the first must be the record'sentry, the record'sartifactarguments must follow it as an ordered subsequence; flags and literals may interleave.mcp run→AB6017when the document reaches a launchable record over another transport.runtime.manifestPathwhilemcp runreadsprojections[host].documents.mcp, so a manifest missing the pointer validated and then failed to launch → the pointer must equal the runtime's document path whenever either exists (AB6017);artifact-inspection-service.test.ts's pointerless-host case now asserts refusal at publish.docs/diagnostics.mdAB6017said "exactly" → describes the ordered rule, the pointer, and the transport case.con.txt,COM¹.log,lpt³rejected by both;COM10.log,console.txt,nulled/index.json,lpt.txtaccepted by both.Re-run on
1ee3cffae5(Sol): no findings.Gates
On
1ee3cffae5(after mergingorigin/mainthrough #634/#646):pnpm build && pnpm typecheck && pnpm lint && pnpm test:unitgreen (4228 passed); the touched integration suites green (234 passed: combined proof, host install proofs, prebuilt payload, web command, compile evidence, artifact validator, dev host install, build, build-compose, install, doctor, uninstall, dev web-host routes, package build);pnpm test:packed(38) green;pnpm docs:site:buildgreen (parity ok). Earlier on66c72f458:pnpm build && pnpm typecheck && pnpm lint && pnpm test:unitgreen (4221 passed); the touched integration suites green (209 passed on1af72fec00: combined proof, host install proofs, prebuilt payload, web command, compile evidence, artifact validator, dev host install, build, build-compose; 172 passed on66c72f458: build-compose, package-build, artifact validator, combined proof);pnpm test:packed(full packed pool, 37) green;pnpm docs:site:buildgreen (0 broken links, parity ok). Fullpnpm test:integration:runlast ran green on2387162ac. Earlier heads:e91b06825,565bd77e5.Hand-offs (posted on #555 and #592 at merge)
distribution.channelsalready carries'npm'whencompiler.project.packageNameis set; add the npm-specific pointers underdistribution(closed keys → schema +parseDistribution+website/docs/*/reference/artifact-manifest.mdx), never underprojections[].INSTALL.md/install.mjs):distribution.install.instructions/.scriptare the pointers;AB6023/AB6024already require the files perbuiltInHosts(selected). Author the contents; keep themfiles[]rows (the writer indexes them automatically). The generatedinstall.mjs(install/surface.ts) still reads.cursor-plugin/plugin.jsonand probes.cursor-plugin/mcp.json/mcp.jsonforruntimeStateRoots(fix: manage derived state across lifecycle #642) because it also installs Agent Plugins packs that have no artifact manifest; when S5 rewrites it, readagent-bundle.manifest.json(projections[cursor].documents.{plugin,mcp}) first and keep the probe only for manifest-less packs.compileris the seam — add policy inputs (cache keys, tool versions, timings) there and bumprecordVersiononly;manifestVersionstays 2.reindexArtifactManifestis the one path that rewrites a manifest outsidebuild.